/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C)N_H 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C)N_H 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.soundrenderer.logic.sound;

import com.magiciansforest.audio.filter.stereo.SoundPosition;

/**
 *
 * @author N_H
 */
public class StereoSoundFilter implements SoundFilter {

    private static final int X = 0;
    private static final int Z = 1;
    private static final int RATIO = 2;
    private static final int L = 0;
    private static final int R = 1;
    private static final int C = 2;
    private static final int BASS = 3;
    private static final int BL = 4;
    private static final int BR = 5;
    private SoundPosition pos = SoundPosition.createSoundPosition_v(0, 0, 0);
    private double[][] spk = new double[6][2];
    private Collide collideAt;
    private double dist;

    public StereoSoundFilter() {
        pos.setPosition_s(0, 0, 1.0);

        //Left speaker position
        spk[L][X] = Math.sin(Math.toRadians(-30));
        spk[L][Z] = Math.cos(Math.toRadians(-30));
        //Right speaker position
        spk[R][X] = Math.sin(Math.toRadians(30));
        spk[R][Z] = Math.cos(Math.toRadians(30));
        //Center speaker position
        spk[C][X] = Math.sin(Math.toRadians(0));
        spk[C][Z] = Math.cos(Math.toRadians(0));
        //BackwardLeft speaker position
        spk[BL][X] = Math.sin(Math.toRadians(-110));
        spk[BL][Z] = Math.cos(Math.toRadians(-110));
        //BackwardRight speaker position
        spk[BR][X] = Math.sin(Math.toDegrees(110));
        spk[BL][Z] = Math.cos(Math.toRadians(110));
    }

    @Override
    public double[] filter(double[] pcmData) {
        double[] ret = new double[pcmData.length * 6];

        if (collideAt == null) {
            for (int i = 0; i < pcmData.length; i++) {
                ret[i * 6 + C] = pcmData[i] / Math.pow(dist, 2);
            }
        } else {
            for (int i = 0; i < pcmData.length; i++) {
                ret[i * 6 + collideAt.spk0] = pcmData[i] * (1.0 - collideAt.ratio) / Math.pow(dist, 2);
                ret[i * 6 + collideAt.spk1] = pcmData[i] * collideAt.ratio / Math.pow(dist, 2);
            }
        }
        
        return ret;
    }

    @Override
    public void setSoundPosition_s(double elev, double azim, double dist) {
        pos.setPosition_s(elev, azim, dist);
        calcCollide();
    }

    @Override
    public void setSoundPosition_v(double x, double y, double z) {
        pos.setPosition_v(x, y, z);
        calcCollide();
    }

    private void calcCollide() {
        double[][] c = new double[6][];
        double[] snd = new double[]{pos.getX(), pos.getZ()};

        c[0] = lineCollide(spk[C], spk[R]);
        c[1] = lineCollide(spk[R], spk[BR]);
        c[2] = lineCollide(spk[BR], spk[BL]);
        c[3] = lineCollide(spk[BL], spk[L]);
        c[4] = lineCollide(spk[L], spk[C]);

        int res = 0;
        for (int i = 0; i < c.length; i++) {
            if (c[i] != null) {
                double d = dist(snd, c[i]);
                if (c[res] == null || (d < dist)) {
                    res = i;
                    dist = d;
                }
            }
        }

        switch (res) {
            case 0:
                collideAt = new Collide(C, R, c[res][RATIO]);
                break;
            case 1:
                collideAt = new Collide(R, BR, c[res][RATIO]);
                break;
            case 2:
                collideAt = new Collide(BR, BL, c[res][RATIO]);
                break;
            case 3:
                collideAt = new Collide(BL, L, c[res][RATIO]);
                break;
            case 4:
                collideAt = new Collide(L, C, c[res][RATIO]);
                break;
            default:
                collideAt = null;
                break;
        }
    }

    private double[] lineCollide(double[] p0, double[] p1) {
        double x = pos.getX();
        double z = pos.getZ();
        double d = (p1[X] - p0[X]) * z - (p1[Z] - p0[Z]) * x;
        if (d == 0) {
            return null;
        }

        double[] ret = new double[3];
        ret[RATIO] = (x * p0[Z] - z * p0[X]) / d;

        if (ret[RATIO] > 1 || ret[RATIO] < 0) {
            return null;
        }

        ret[X] = p0[X] + ret[RATIO] * (p1[X] - p0[X]);
        ret[Z] = p0[Z] + ret[RATIO] * (p1[Z] - p0[Z]);
        return ret;
    }

    private double dist(double[] p0, double[] p1) {
        return Math.sqrt(Math.pow(p1[X] - p0[X], 2) + Math.pow(p1[Z] - p0[Z], 2));
    }

    private class Collide {

        private int spk0, spk1;
        private double ratio;

        public Collide(int spk0, int spk1, double ratio) {
            this.spk0 = spk0;
            this.spk1 = spk1;
            this.ratio = ratio;
        }

        public double getPos() {
            return ratio;
        }

        public int getSpk0() {
            return spk0;
        }

        public int getSpk1() {
            return spk1;
        }
    }
}
